<?php
/*********************************************
  CPG DragonflyCMS, Copyright (c) 2011 by DragonflyCMS Dev Team
  http://dragonflycms.org

  Dragonfly is released under the terms and conditions
  of the GNU GPL version 2 or any later version

  $Revision$
  $Date$
  $Source$
**********************************************/

// little dev helper, duplicate function in coppermine!
//function print_debug($b, $continue=false){
//	echo'<pre>';$type=gettype($b);
//	switch($type){case'array':case'object':case'resource':print_r($b);break;default:var_dump($b);}
//	echo'</pre>';if(!$continue){exit;}
//}

/**
 DFSubject as SplSubject
 Notifies observers
**/
interface DFSubject
{
	# Get a subject to include an observer
	public function attach($observer);
	# Get a subject to exclude an observer
	public function detach($observer);
	public function notify($subject=__CLASS__);
}

/**
 DFService as SplObserver
 Be notified by subjects
**/
interface DFService
{
	# Let the Subject knows your priority
	# 1 highest, 99 lowest. 1-10 and 90-99 are reserved.
	public function priority();
	public function runlevel();
	public function update();
}

class DF implements DFSubject
{

	const BOOT_DOWN   = 0; // register_shutdown_function
	const BOOT_CORE   = 1; // filters-php-debugger-error-loader
	const BOOT_BASE   = 2; // cmsinit.inc-db-session-maincfg-url-language-hooks
	const BOOT_ETH    = 3; // client-security-networking-hooks
	const BOOT_CUSTOM = 4; // N/A
	const BOOT_USER   = 5; // tpl-output-module-blocks-hooks
	const BOOT_ERROR  = 6; // hooks-error-reboot(redirect)

	# the current working state (runlevel)
	private $current_state;

	# services container
	# group by runlevel
	# execute by priority
	private $services=array(
		self::BOOT_DOWN   => array(),
		self::BOOT_CORE   => array(),
		self::BOOT_BASE   => array(),
		self::BOOT_ETH    => array(),
		self::BOOT_CUSTOM => array(),
		self::BOOT_USER   => array(),
		self::BOOT_ERROR  => array()
	);

	public function runlevel() { return 1; }

	public function setState($state)
	{
		$this->current_state = $state;
		$this->notify();
	}

	public function getState()
	{
		return $this->current_state;
	}

	public function attach($observer)
	{
		if (!is_object($GLOBALS[$observer])) return;
		$GLOBALS[$observer]->subject = __CLASS__;
		$hash = spl_object_hash($GLOBALS[$observer]);
		$priority = $GLOBALS[$observer]->priority();
		$runlevel = $GLOBALS[$observer]->runlevel();
		$runlevel = is_array($runlevel) ? $runlevel : array($runlevel);
		foreach ($runlevel as $i) {
			if (!isset($this->services[$i][$priority][$hash])) {
				$this->services[$i][$priority][$hash] = $observer;
				if ($i === $this->current_state) $GLOBALS[$observer]->update();
		}}
	}

	public function detach($observer)
	{
		if (!isset($GLOBALS[$observer])) return;
		$hash = spl_object_hash($GLOBALS[$observer]);
		$priority = $GLOBALS[$observer]->priority();
		$runlevel = $GLOBALS[$observer]->runlevel();
		$runlevel = is_array($runlevel) ? $runlevel : array($runlevel);
		foreach ($runlevel as $i) {
			if (isset($this->services[$i][$priority][$hash])) {
				$GLOBALS[$observer]->update();
				unset($this->services[$i][$priority][$hash], $GLOBALS[$observer]);
		}}
	}

	// may be broken
	public function detachAll()
	{
		if ($this->current_state !== DF::BOOT_DOWN) { /* premature shutdown */ }
		while ($priority = array_pop($this->services)) {
			ksort($priority);
			while ($data = array_pop($priority)) {
				$this->detach($data[key($data)]);
		}}
	}

	# DF will only notify about runlevel state changes
	public function notify($subject=__CLASS__)
	{
		$i=$this->current_state;
		$notified = array();
		if (empty($this->services[$i])) return;
		# notify from higher to lower priority ( 0 -> 100 )
		ksort($this->services[$i]);
		$pdata['subject'] = $subject;
		$pdata['state'] = $this->current_state;
		foreach ($this->services[$i] as $priority => $hash__class) {
			$hash = key($hash__class);
			$class = $hash__class[$hash];
			# notify only once for the current runlevel
			if (!isset($notified[$hash])) {
				$notified[$hash] = $class;
				# when update() returns true propagation will be halted for the current level
				$GLOBALS[$class]->update();
			}
		}
	}

	# autoload() ads a few milliseconds on each call
	public function autoLoad($class_name)
	{
		# avoids endless loops and resultant inproperly closed classes
//		if (DF::BOOT_DOWN === $this->current_state) { return; }

		//trigger_error('Autoload: '.$class_name);
		# split class_name into segments where the
		# first segment is the library or component
//		$class_name = ltrim($class_name,'\\');
		if (strpos($class_name, '\\')) {
			$path = explode('\\', $class_name);
			$namespace = implode('\\', array_slice($path,0,-1));
		} else {
			$path = explode('_', $class_name);
			$namespace = null;
		}
/*
		if (!preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#', $class_name)) {
			# PEAR bug and such
			return;
		}
*/
		if ($path)
		{
			if (empty($path[1])) {
				// Try Dragonfly legacy class
				$file = CLASS_PATH.strtolower($class_name).'.php';
				if (is_file($file)) {
					include_once($file);
					if (DF_MODE_DEVELOPER) { trigger_error('Autoloading class: '. $class_name, E_USER_NOTICE); }
					return;
				}
			}

			switch (strtolower($path[0]))
			{
			case 'zend': include(implode('/',$path).'.php');
			case 'pear': break;

			default:
				/** Default spl_autoload_register also lowercases the filename  */
				$path = array_map('strtolower', $path);

				/** When the class name is the directory itself add itself as filename  */
				if (!isset($path[2])) { $path[2] = $path[1]; }

				/** Temporary disable "failed to open stream: No such file or directory" warning */
				global $cpgdebugger;
				$level = $cpgdebugger->error_level;
				$cpgdebugger->error_level = $level & ~E_WARNING;
				$log_level = $cpgdebugger->error_level;
				$cpgdebugger->log_level = $log_level & ~E_WARNING;

				include(implode('/',$path).'.php');

				if (!class_exists($class_name, false) && !interface_exists($class_name, false))
				{
					/** Last attempt to find class in global container directory */
					$lib = $path[0];
					array_shift($path);
					if ($path[0] === $path[1]) { array_shift($path); }
					$file = implode('_',$path);
					if ($interface = in_array('interface', $path)) {
						$file = $lib.'/interfaces/'.str_replace('_interface','',$file);
					} else {
						$file = $lib.'/classes/'.$file;
					}
					include($file.'.php');
					$cpgdebugger->error_level = $level;
					$cpgdebugger->log_level = $log_level;
					if ($interface && !interface_exists($class_name, false)) {
						if (class_exists('Poodle_PHP_INI', false)) Poodle_PHP_INI::set('display_errors', 1);
						eval('interface '.$class_name.'{}');
//						throw new Exception("Interface {$class_name} not found");
					}
					if (!$interface && !class_exists($class_name, false)) {
						// Try Dragonfly legacy class
						$file = CLASS_PATH.strtolower($class_name).'.php';
						if (is_file($file)) {
							include_once($file);
							if (class_exists($class_name, false)) { return; }
						}
						if (class_exists('Poodle_PHP_INI', false)) Poodle_PHP_INI::set('display_errors', 1);
						throw new Exception("Class {$class_name} not found");
					}
				}
				error_reporting($level);
				break;
			}
		}
	}
/*
	public function autoLoad($class_name)
	{
		# avoids endless loops and resultant inproperly closed classes
		if (DF::BOOT_DOWN === $this->current_state) { return; }
		if (!preg_match('#^[a-z0-9\-_\\\]+$#i', $class_name)) { return; }
		$name = strtolower($class_name);
		/*$class_name = str_replace('core\\', '', $name);* /
		if (is_file(CLASS_PATH.$name.'.php')) {
			require_once(CLASS_PATH.$name.'.php');
			if (DF_MODE_DEVELOPER) { trigger_error('Autoloading class: '. $class_name, E_USER_NOTICE); }
		/*} else if (false !== strpos($name, '\\') && is_file(BASEDIR. str_replace('\\', '/', $name). '.php')) {
			require_once(BASEDIR. str_replace('\\', '/', $name). '.php');
			if (DF_MODE_DEVELOPER) trigger_error('Autoloading namespaced class: '. $class_name, E_USER_NOTICE);* /
		} else if (0 === strpos($class_name, 'DF_')) {
			$array = explode('_', $name);
			$file = CLASS_PATH.$array[1].(isset($array[2])?DS.$array[2]:'').'.php';
			if (is_file($file)) {
				require_once($file);
				if (DF_MODE_DEVELOPER) trigger_error('Autoloading class: '. $class_name);
			}
		}
	}
*/
}

class PerformanceLog implements DFService
{

	private static $data = array();
	private static $next = 0;
	private static $partial_time = 0;
	private static $partial_memory = 0;

	public $subject;

	public static function start()
	{
		self::$data[0]['file'] = str_replace(BASEDIR, '', __FILE__);
		self::$data[0]['line'] = __LINE__;
		self::$data[0]['time'] = 0;
		self::$data[0]['memory'] = 0;
		++self::$next;
	}

	public static function stop()
	{
		if (1 === self::$next) return '';
		$o = 'Performance Log: <br />';
		//array_shift(self::$data);
		while ($data = array_shift(self::$data)) {
			$f = $data['file']; $l = $data['line'];
			$t = round($data['time']*1000,1);
			$m = round((round($data['memory'] / 1048576 * 100) / 100), 2);
			$o.= sprintf('%1$s at line %2$s: in %3$s ms, with %4$s MB<br />', $f, $l, $t, $m);
			$data = $f = $l = $t = $m = null;
		}
		$o .= 'with a peak of '. round((round(memory_get_peak_usage() / 1048576 * 100) / 100), 2). 'MB';
		return $o;
	}

	public static function splitTime()
	{
		$backtrace = debug_backtrace(false);
		while ($callee = array_pop($backtrace)) {
			if ((isset($callee['class']) && 'DF' === $callee['class']) || __FUNCTION__ === $callee['function']) break;
		}
		$t = microtime(true) - START_TIME - self::$partial_time;
		$m = memory_get_usage() - START_MEMORY_USAGE - self::$partial_memory;
		self::$data[self::$next] = array(
			'file' => str_replace(BASEDIR, '', $callee['file']),
			'line' => $callee['line'],
			'time' => $t,
			'memory' => $m
		);
		self::$partial_time += $t;
		self::$partial_memory += $m;
		++self::$next;
	}

	public function name()     { return __CLASS__ ; }
	public function priority() { return 89; }
	public function runlevel() { return array(DF::BOOT_CORE, DF::BOOT_BASE, DF::BOOT_ETH, DF::BOOT_USER); }
	public function update() {
		global $DF;
		switch ($DF->getState()) {
			case DF::BOOT_CORE: return self::start();
			//case DF::BOOT_DOWN: case DF::BOOT_ERROR: return self::stop();
			case DF::BOOT_BASE: case DF::BOOT_ETH: return self::splitTime();
			default: self::splitTime();
		}
		return;
	}

}

function Prepare_INPUT(&$value, $key) {
    if (is_array($value)) { array_walk($value, 'Prepare_INPUT', false); }
    else {
        $value = preg_replace('#\p{Zs}#u', ' ', $value);
    }
}

